package org.toves.checklua.warnings;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import org.toves.checklua.ast.SimpleProcessorResult;

class UnknownReadResult extends SimpleProcessorResult {
	private static final List<String> otherChecks
		= Arrays.asList(new String[] { "true", "false", "nil" });
	
	private UnknownReadProcessor context;
	private HashSet<String> newGlobals;
	private HashSet<String> locals;
	private ArrayList<HashSet<String>> localStack;
	
	UnknownReadResult(UnknownReadProcessor context) {
		this.context = context;
		this.newGlobals = new HashSet<String>();
		this.locals = new HashSet<String>();
		this.localStack = new ArrayList<HashSet<String>>();
	}
	
	void pushLevel() {
		localStack.add(null);
	}
	
	void addLocal(String symbol) {
		if (!locals.contains(symbol)) {
			locals.add(symbol);
			HashSet<String> locs = localStack.get(localStack.size() - 1);
			if (locs == null) {
				locs = new HashSet<String>();
				localStack.set(localStack.size() - 1, locs);
			}
			locs.add(symbol);
		}
	}
	
	void popLevel() {
		HashSet<String> locs = localStack.remove(localStack.size() - 1);
		if (locs != null) {
			for (String loc : locs) {
				locals.remove(loc);
			}
		}
	}
	
	void addGlobal(String symbol) {
		if (!context.containsGlobal(symbol) && !locals.contains(symbol)) {
			newGlobals.add(symbol);
		}
	}
	
	boolean contains(String symbol) {
		return context.containsGlobal(symbol) || locals.contains(symbol)
			|| newGlobals.contains(symbol);
	}
	
	String[] findClosestMatch(String s) {
		String sCaps = s.toUpperCase();
		int bestDist = 2 * s.length() + 1;
		String bestCap = null;
		int[][] bestMatrix = null;
		String best = null;
		Iterable<?>[] toCheck
			= { otherChecks, context.getGlobals(), newGlobals, locals };
		for (Iterable<?> check : toCheck) {
			for (Object tObj : check) {
				String t = (String) tObj;
				if (t.toUpperCase().equals(sCaps)) {
					bestCap = t;
				}
				int[][] distMatrix = getEditDistance(s, t);
				int d = distMatrix[0][0];
				if (d < bestDist) {
					best = t;
					bestDist = d;
					bestMatrix = distMatrix;
				}
			}
		}
		if (bestDist <= 7 && bestDist <= s.length()) {
			return new String[] { best, decodeMatrix(bestMatrix, s, best) };
		} else if (bestCap != null) {
			return new String[] { bestCap, "watch capitalization" };
		} else {
			return null;
		}
	}

	@Override
	public void commit() {
		for (String symbol : newGlobals) {
			context.addGlobal(symbol);
		}
	}
	
	private static int[][] getEditDistance(String s, String t) {
		int m = s.length();
		int n = t.length();
		int[][] d = new int[m + 1][n + 1];
		int[][] ops = new int[m + 1][n + 1];
		for (int i = 0; i <= m; i++) { // deletion
			d[i][0] = i;
			ops[i][0] = 1;
		}
		for (int j = 0; j <= n; j++) { // insertion
			d[0][j] = j;
			ops[0][j] = 2;
		}
		for (int j = 1; j <= n; j++) {
			for (int i = 1; i <= m; i++) {
				char a = s.charAt(i - 1);
				char b = t.charAt(j - 1);
				if (a == b) {
					d[i][j] = d[i - 1][j - 1];
					ops[i][j] = 0;
				} else {
					int x = d[i - 1][j] + 2; // deletion
					int y = d[i][j - 1] + 2; // insertion
					int z = d[i - 1][j - 1] + 2; // substitution
					if (Character.toUpperCase(a) == Character.toUpperCase(b)) {
						z--;
					}
					int min;
					if (y < z) {
						min = x < y ? x : y;
					} else {
						min = x < z ? x : z;
					}
					d[i][j] = min;
					if (min == x) {
						ops[i][j] = 1;
					} else if (min == y) {
						ops[i][j] = 2;
					} else if (min == z) {
						ops[i][j] = min == d[i - 1][j - 1] + 2 ? 3 : 4;
					}
				}
			}
		}
		ops[0][0] = d[m][n];
		return ops;
	}
	
	private static String decodeMatrix(int[][] ops, String s, String t) {
		String diff = null;
		int i = s.length();
		int j = t.length();
		while ((i > 0 || j > 0) && i >= 0 && j >= 0) {
			String msg = null;
			switch(ops[i][j]) {
			case 0: i--; j--; break;
			case 1: i--; msg = "delete " + s.charAt(i); break;
			case 2: j--; msg = "add " + t.charAt(j); break;
			case 3: i--; j--;
				msg = "change " + s.charAt(i) + " to " + t.charAt(j);
				break;
			case 4: i--; j--;
				if (Character.isUpperCase(s.charAt(i))) {
					msg = "uncapitalize " + s.charAt(i);
				} else {
					msg = "capitalize " + s.charAt(i);
				}
				break;
			default: i--; j--; // should never happen
			}
			if (msg != null) {
				if (diff == null) {
					diff = msg;
				} else {
					diff = msg + ", " + diff;
				}
			}
		}
		return diff;
	}
}
